#include "base_game_impl.h"
#include "service.h"
#include "listener.h"
#include "client_connection.h"
#include "game_base_msg.h"
#include "jmsg.h"
#include "base_player.h"
#include "base_game.h"
#include "base_room.h"
#include "base_desk.h"
#include "db_client.h"

BaseGameImpl::BaseGameImpl(void)
{
}

BaseGameImpl::~BaseGameImpl(void)
{
}

void BaseGameImpl::onStart()
{
	
}

void BaseGameImpl::onAccept( Connection* conncetion )
{
	ClientConnection* clientHandler = new ClientConnection;
	clientHandler->init(m_service, conncetion);
	clientHandler->setHandler(this);
	m_setUnAuthConnections.insert(clientHandler);
}

// will not happen forever
void BaseGameImpl::onClientConnected( ClientConnection* conn ){}

void BaseGameImpl::onClientData( ClientConnection* conn, int mainCmd, int subCmd, void* data, int len )
{
	if(mainCmd == 1) {
		if(subCmd == 0) {
			this->procRoomSystemMsg(conn, data, len);
		} else {
			this->procRoomUserMsg(conn, data, len);
		}
	} else {
		if(subCmd == 0) {
			this->procDeskSystemMsg(conn, data, len);
		} else {
			this->procDeskUserMsg(conn, data, len);
		}
	}
}

bool BaseGameImpl::onClientDisconnected( ClientConnection* conn )
{
	auto player = (BasePlayer*)conn->getUserData();

	if (!player) {
		conn->close();
		return false;
	}

	if (player->m_desk) {
		player->m_desk->procPlayerLeave(player);
	}

	if (player->m_room) {
		player->m_room->procPlayerLeave(player);
	}

	detachConnectionFromPlayer(player);
	conn->close();
	return true;
}

void BaseGameImpl::procRoomSystemMsg( ClientConnection* conn, void* data, int len )
{
	JMsgReader reader((unsigned char*)data, len);
	bool isSuccess = false;
	int typeId = reader.peekMessageTypeId(isSuccess);

	if(typeId == kBaseMsgC2SLogin) {
		BaseMsgC2SLogin msg;
		msg.decode(m_proto, &reader);
		this->procPlayerLoginMsg(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SRegister) {
		BaseMsgC2SRegister msg;
		msg.decode(m_proto, &reader);
		this->procPlayerRegister(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SJoinRoom) {
		BaseMsgC2SJoinRoom msg;
		msg.decode(m_proto, &reader);
		this->procPlayerJoinRoomMsg(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SActivate) {
		BaseMsgC2SActivate msg;
		msg.decode(m_proto, &reader);
		this->procPlayerActivate(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SUpdateRoomStatus) {
		BaseMsgC2SUpdateRoomStatus msg;
		msg.decode(m_proto, &reader);
		this->procPlayerUpdateRoomStatus(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SLeaveRoom) {
		BaseMsgC2SLeaveRoom msg;
		msg.decode(m_proto, &reader);
		this->procPlayerLeaveRoom(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SUpdatePlayerData) {
		BaseMsgC2SUpdatePlayerData msg;
		msg.decode(m_proto, &reader);
		this->procPlayerUpdatePlayerInfo(conn, &msg);
	}
}

void BaseGameImpl::procPlayerLoginMsg( ClientConnection* conn, BaseMsgC2SLogin* msg )
{
	if(conn->getUserData()) {
		return;
	}

	auto player = attachConnectionAndCreateUser(conn, msg->userName);

	if (player) {
		m_dbClient->validatePlayerLogin(msg->userName, msg->password, msg->isGuest);
	}
	else { // player has login
		BaseMsgS2CLogin ret;
		ret.success = false;
		ret.userName = msg->userName;
		JMsgWriter writer;
		ret.encode(m_proto, &writer);
		conn->SendData(1, 0, writer.getBuffer(), writer.getBufferLen());
	}	
}

void BaseGameImpl::procPlayerJoinRoomMsg(ClientConnection * conn, BaseMsgC2SJoinRoom * msg)
{
	if (!conn->getUserData()) {
		return;
	}
	BaseMsgS2CJoinRoom resp;
	BasePlayer* player = (BasePlayer*)conn->getUserData();
	if (player->m_room) {		
		resp.isSuccess = false;
		player->sendMessage(1, resp);
		return;
	}
	m_room->procPlayerJoin(player);
}

void BaseGameImpl::procPlayerJoinDeskMsg(ClientConnection * conn, BaseMsgC2SJoinDesk * msg)
{
	if (!conn->getUserData()) {
		return;
	}
	BaseMsgS2CJoinDesk resp;
	BasePlayer* player = (BasePlayer*)conn->getUserData();

	// todo: add error code for join room
	if (!player->m_room) {
		resp.success = false;
		goto out;
	}

	if (player->m_desk) {
		resp.success = false;
		goto out;
	}

	player->m_room->onPlayerRequestJoinDesk(player, msg->deskId, msg->chairId);
	return;
out:
	player->sendMessage(1, resp);

}

void BaseGameImpl::procPlayerCreateFriendDeskMsg(ClientConnection * conn, BaseMsgC2SCreateFriendDesk * msg)
{
	if (!conn->getUserData()) {
		return;
	}
	BaseMsgS2CJoinDesk resp;
	BasePlayer* player = (BasePlayer*)conn->getUserData();

	// todo: add error code for join room
	if (!player->m_room) {
		resp.success = false;
		goto out;
	}

	if (player->m_desk) {
		resp.success = false;
		goto out;
	}

	player->m_room->procPlayerCreateFriend(player);
	return;
out:
	player->sendMessage(1, resp);

}

void BaseGameImpl::procPlayerJoinFriendDeskMsg(ClientConnection * conn, BaseMsgC2SJoinFriendDesk * msg)
{
	if (!conn->getUserData()) {
		return;
	}
	BaseMsgS2CJoinDesk resp;
	BasePlayer* player = (BasePlayer*)conn->getUserData();

	// todo: add error code for join room
	if (!player->m_room) {
		resp.success = false;
		goto out;
	}

	if (player->m_desk) {
		resp.success = false;
		goto out;
	}

	player->m_room->procPlayerJoinFriend(player, msg->deskCode);
	return;
out:
	player->sendMessage(1, resp);

}

void BaseGameImpl::procPlayerRegister(ClientConnection * conn, BaseMsgC2SRegister * msg)
{

	auto player = attachConnectionAndCreateUser(conn, msg->userName);

	if (player) {
		DBMsgAddRegisterPlayerRequest dbMsg;
		dbMsg.userName = msg->userName;
		dbMsg.passwordSha1 = msg->passwordSha1;
		m_dbClient->sendMsg(dbMsg);
	}
	else {
		BaseMsgS2CRegister ret;
		ret.userName = msg->userName;
		ret.isSuccess = false;
		JMsgWriter writer;
		ret.encode(m_proto, &writer);
		conn->sendDBData(1, 0, writer.getBuffer(), writer.getBufferLen());
	}
}

void BaseGameImpl::procPlayerUpdateRoomStatus(ClientConnection* conn, BaseMsgC2SUpdateRoomStatus* msg) {
	if (!conn->getUserData()) {
		return;
	}
	BaseMsgS2CUpdateRoomStatus resp;
	BasePlayer* player = (BasePlayer*)conn->getUserData();

	if (!player->m_room) {
		resp.success = false;
	}
	else {
		resp.success = true;
		player->m_room->generateRoomStatusMsg(&resp.roomStatus);
	}
	player->sendMessage(1, resp);
}

void BaseGameImpl::procPlayerLeaveRoom(ClientConnection* conn, BaseMsgC2SLeaveRoom * msg)
{
	if (!conn->getUserData()) {
		return;
	}
	BasePlayer* player = (BasePlayer*)conn->getUserData();

	if (player->m_desk) {
		player->m_desk->procPlayerLeave(player);
	}

	if (player->m_room) {
		player->m_room->procPlayerLeave(player);
	}	
}

void BaseGameImpl::procPlayerUpdatePlayerInfo(ClientConnection * conn, BaseMsgC2SUpdatePlayerData * msg)
{
	if (!conn->getUserData()) {
		return;
	}
	BasePlayer* player = (BasePlayer*)conn->getUserData();

	player->m_nickName = msg->nickName;
	
	BaseMsgS2CUpdatePlayerInfo ret;
	ret.success = true;
	ret.nickName = msg->nickName;
	ret.phoneNo = msg->phoneNo;
	ret.userName = player->m_userName;
	player->sendMessage(1, ret);
	
	DBMsgSavePlayerInfo dbMsg;
	dbMsg.playerInfo.nickName = msg->nickName;
	dbMsg.playerInfo.phoneNo = msg->phoneNo;
	dbMsg.playerInfo.userName = player->m_userName;
	m_dbClient->sendMsg(dbMsg);
}

BasePlayer * BaseGameImpl::attachConnectionAndCreateUser(ClientConnection * conn, std::string userName)
{
	if (m_userNameToPlayer.find(userName) != m_userNameToPlayer.end()) {
		return nullptr;
	}

	if (this->m_setUnAuthConnections.find(conn) != m_setUnAuthConnections.end()) {
		m_setUnAuthConnections.erase(conn);
	}

 	BasePlayer* player = m_baseGame->onCreatePlayer();
	player->m_userName = userName;
	player->m_connection = conn;
	player->m_proto = m_proto;
	m_userNameToPlayer[userName] = player;
	conn->setUserData(player);
	return player;
}

void BaseGameImpl::detachConnectionFromPlayer(BasePlayer* player, bool addConnToUnAuth)
{
	auto iter = m_userNameToPlayer.find(player->m_userName);

	if (iter != m_userNameToPlayer.end()) {
		m_userNameToPlayer.erase(iter);
	}

	if (addConnToUnAuth && player->m_connection && m_setUnAuthConnections.find(player->m_connection) == m_setUnAuthConnections.end()) {
		m_setUnAuthConnections.insert(player->m_connection);
	}
	player->m_connection->setUserData(NULL);
	delete player;
}

void BaseGameImpl::onStop()
{

}

void BaseGameImpl::onClientHeartBeatTimeout( ClientConnectionHandler* handler )
{

}

void BaseGameImpl::onClientDisconnect(ClientConnection* conn)
{
	auto player = (BasePlayer*)conn->getUserData();

	if (!player) {
		return;
	}

	auto iter = m_setUnAuthConnections.find(conn);
	m_setUnAuthConnections.erase(iter);
	detachConnectionFromPlayer(player, false);
}

void BaseGameImpl::onDBConnected( DBClient* client )
{

}

void BaseGameImpl::onDBDisconnected( DBClient* client )
{

}

void BaseGameImpl::procRoomUserMsg( ClientConnection* conn, void* data, int len )
{
	BasePlayer* player = (BasePlayer*)conn->getUserData();
	if (!player) {
		// do not process room userdata before user send login request
		return;
	}

	if (!player->m_room) {
		// user havent validate
		return;
	}
	player->m_room->onPlayerMessage(player, data, len);
}

void BaseGameImpl::procDeskSystemMsg( ClientConnection* conn, void* data, int len )
{
	JMsgReader reader((unsigned char*)data, len);
	bool isSuccess = false;
	int typeId = reader.peekMessageTypeId(isSuccess);

	if (typeId == kBaseMsgC2SJoinDesk) {
		BaseMsgC2SJoinDesk msg;
		msg.decode(m_proto, &reader);
		this->procPlayerJoinDeskMsg(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SLeaveDesk) {
		BaseMsgC2SLeaveDesk msg;
		msg.decode(m_proto, &reader);
		this->procPlayerLeaveDeskMsg(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SCreateFriendDesk) {
		BaseMsgC2SCreateFriendDesk msg;
		msg.decode(m_proto, &reader);
		this->procPlayerCreateFriendDeskMsg(conn, &msg);
	}
	else if (typeId == kBaseMsgC2SJoinFriendDesk) {
		BaseMsgC2SJoinFriendDesk msg;
		msg.decode(m_proto, &reader);
		this->procPlayerJoinFriendDeskMsg(conn, &msg);
	}
}

void BaseGameImpl::procPlayerLeaveDeskMsg(ClientConnection* conn, BaseMsgC2SLeaveDesk* msg) {
	BasePlayer* player = (BasePlayer*)conn->getUserData();
	if (!player) {
		// do not process room userdata before user send login request
		return;
	}

	if (!player->m_desk) {
		return;
	}
	auto desk = player->m_desk;
	desk->procPlayerLeave(player);
}

void BaseGameImpl::procDeskUserMsg( ClientConnection* conn, void* data, int len )
{
	BasePlayer* player = (BasePlayer*)conn->getUserData();
	if (!player) {
		// do not process room userdata before user send login request
		return;
	}

	if (!player->m_desk) {
		// user have not validate yet
		return;
	}

	player->m_desk->onPlayerData(player, data, len);
}

void BaseGameImpl::start()
{
	m_proto = game_base_msgCreateProto();
	m_dbProto = db_protoCreateProto();
	m_service = new Service;
	m_listener = new Listener("0.0.0.0", 8128, m_service, this);
	m_listener->startListen();

	m_dbClient = new DBClient(m_service, "127.0.0.1", 8129);
	m_dbClient->setHandler(this);
	
	m_room = m_baseGame->onCreateRoom();
	m_room->m_roomId = 0;

	for(int i = 0; i < 100; i++) {
		auto desk = m_baseGame->onCreateDesk();
		desk->m_id = i;
		desk->m_proto = m_proto;
		m_room->addDesk(desk);
	}

	m_service->start();
}

void BaseGameImpl::setBaseGame( BaseGame* baseGame )
{
	m_baseGame = baseGame;
}

void BaseGameImpl::onDBPlayerInfoResponse( DBMsgPlayerInfoResponse* response )
{
	std::map<string, BasePlayer*>::iterator iter = m_userNameToPlayer.find(response->userName);
	if(iter == m_userNameToPlayer.end()) {
		return;
	}

	BasePlayer* basePlayer = iter->second;

	if (!response->isSuccess) {
		BaseMsgS2CLogin msg;
		msg.success = false;
		msg.userName = response->userName;
		basePlayer->sendMessage(1, msg);
		detachConnectionFromPlayer(basePlayer, true);
		return;
	}

	if(basePlayer->m_isLogin) {
		return;
	}

	basePlayer->m_isGuest = response->playerInfo.isGuest ? true : false;
	basePlayer->m_isLogin = true;
	basePlayer->m_nickName = response->playerInfo.nickName;
	basePlayer->m_score = response->playerInfo.score;	

	BaseMsgS2CLogin msg;
	msg.success = true;
	msg.userName = response->userName;

	BaseMsgPlayerInfo& playerInfo = msg.playerInfo;
	DBMSGPlayerInfo& dbPlayerInfo = response->playerInfo;

	playerInfo.userName = response->userName;
	playerInfo.isGuest = dbPlayerInfo.isGuest ? true : false;
	playerInfo.nickName = dbPlayerInfo.nickName;
	playerInfo.phoneNo = dbPlayerInfo.phoneNo;
	playerInfo.score = dbPlayerInfo.score;
	playerInfo.lastLoginTime = dbPlayerInfo.lastLoginTime;

	BaseProtoRoomState roomState;
	m_room->generateRoomStatusMsg(&roomState);
	msg.rooms.push_back(roomState);
	basePlayer->sendMessage(1, msg);
}

void BaseGameImpl::onDBMsgAddRegisterPlayerResponse(DBMsgAddRegisterPlayerResponse * resp)
{
	std::map<string, BasePlayer*>::iterator iter = m_userNameToPlayer.find(resp->userName);
	if (iter == m_userNameToPlayer.end()) {
		return;
	}

	BasePlayer* basePlayer = iter->second;
	BaseMsgS2CRegister msg;
	msg.userName = resp->userName;
	msg.isSuccess = resp->isSuccess;
	msg.errorCode = resp->errorCode;
	
	basePlayer->sendMessage(1, msg);
	detachConnectionFromPlayer(basePlayer, true);	
}

void BaseGameImpl::onDBMsgActivatePlayerResponse(DBMsgActivatePlayerResponse * resp)
{
	auto iter = m_userNameToPlayer.find(resp->guestUserName);
	if (iter == m_userNameToPlayer.end()) {
		return;
	}

	auto player = iter->second;

	if (!player->m_isLogin) {
		return;
	}

	player->m_userName = resp->newUserName;
	player->m_isGuest = false;

	BaseMsgS2CActivate msg;
	msg.errorCode = resp->errorCode;
	msg.isSuccess = resp->isSuccess;
	msg.newUserName = resp->newUserName;
	player->sendMessage(1, msg);
}

void BaseGameImpl::procPlayerActivate(ClientConnection* conn, BaseMsgC2SActivate* msg) {
	auto player = (BasePlayer*)conn->getUserData();

	if (!player || !player->m_isLogin || !player->m_isGuest) {
		return;
	}	

	DBMsgActivatePlayerRequest req;
	req.newUserName = msg->userName;
	req.newPassword = msg->passwordSha1;
	req.guestUserName = msg->guestUserName;
	req.phoneNo = msg->phoneNo;
	m_dbClient->sendMsg(req);
}


